-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reusable blocks: Improve UX for non-privileged users #12378
Conversation
a1856af
to
9a4cb69
Compare
This should be ready to review!
|
9a4cb69
to
3c1db96
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the canUser function, it's a nice way to express that functionality.
I think that flash of the disabled state of the block settings menu item is something that needs to be fixed.
Preloading could work, but I think checking the resolution state is also possible and worth doing as well, since it shouldn't just depend on preloading. I think this is what embeds do:
https://github.com/WordPress/gutenberg/blob/master/packages/core-data/src/selectors.js#L41
@@ -80,6 +83,7 @@ export default compose( [ | |||
! isReusableBlock( blocks[ 0 ] ) || | |||
! getReusableBlock( blocks[ 0 ].attributes.ref ) | |||
), | |||
canCreateBlocks: canUser( 'create', 'blocks' ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For media upload permissions the media options request is preloaded. I think it might be worth doing the same here.
I noticed a 'flash' where the menu item went from being enabled to disabled when opening the block settings menu while the blocks options request was in progress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌 I've added this OPTIONS request to the list of requests we preload in bc0709f.
Noting that this is a change we'll need to make to core after this PR is merged.
packages/editor/src/components/block-settings-menu/reusable-block-convert-button.js
Outdated
Show resolved
Hide resolved
* | ||
* @return {boolean} Whether or not the user can perform the action. | ||
*/ | ||
export function canUser( state, action, resource, id ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the expressiveness of the canUser selector 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Props to @gziolo for suggesting the API 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad you like it :)
return { | ||
reusableBlock: block && isReusableBlock( block ) ? getReusableBlock( block.attributes.ref ) : null, | ||
id, | ||
isDisabled: !! id && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think same comment here regarding disabling vs. hiding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also noting that in testing I didn't see the flash of this switching from enabled to disabled. That seems to be because the GET request for the block has already been made and cached. If an OPTIONS request was made instead of a GET request the issue would resurface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have changed this to remove the button instead of disabling in 708a50484fc99537a5f3368b4dbb66e134198741.
We can't preload these requests because we don't know the reusable block ID ahead of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I do understand that we can't preload. My worry is that it's only by coincidence that the user's capability is known ahead of time. If the underlying code switches to use OPTIONS instead of GET, users would start seeing this button initially be clickable but then suddenly disappear.
This is probably a nitpick though and not a blocker. I'm not even sure what the best UX would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, got it! Not sure if defaulting to disabled versus defaulting to enabled would be a better experience. Both aren't ideal. I matched the existing hasUploadPermissions
behaviour which defaults to enabled.
I think that, further down the track, core-data
should modify the canUser
state whenever it receives an Allow
header from any response, and not just when the canUser()
resolver is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's a nice idea. Not sure how it'd work, but a nice idea 😄
path, | ||
// Ideally this would always be an OPTIONS request, but unfortunately there's | ||
// a bug in the REST API which causes the Allow header to not be sent on | ||
// OPTIONS requests to /posts/:id routes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that just for /post/:id of for the all resources? I mentioned that because the logic below happens for all resources (so doesn't match the comment).
Is there a ticket/issue covering the bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only checked posts but I suspect the bug affects all REST endpoints. Will do some investigating and open up a Trac ticket 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is the case, it should be fixed /cc @danielbachhuber @kadamwhite
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you just want the Allow
header, what about doing a HEAD
request instead of GET
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking into this further:
- The
Allow
header is generated byrest_send_allow_header()
, which is called onrest_post_dispatch
. rest_post_dispatch
is called for every response (I think):
It seems like Allow
should be present for OPTIONS
. It's an officially supported header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
Can you provide steps to reproduce?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I briefly debugged this when I noticed the issue and from memory it was something to do with rest_handle_options_request
not calling $request->set_url_params()
unlike dispatch
which correctly parses and sets URL parameters.
Can you provide steps to reproduce?
- Create a reusable block
- Perform a
GET http://local.wordpress.test/wp-json/wp/v2/blocks
request and note the ID of the reusable block - Perform a
OPTIONS http://local.wordpress.test/wp-json/wp/v2/blocks/27
request, replacing27
with the ID from step 2. Note that anAllow
header is not sent back - Perform a
GET http://local.wordpress.test/wp-json/wp/v2/blocks/27
request, replacing27
with the ID from step 2. Note that anAllow
header is sent back
Above steps also work with posts: just change blocks
to posts
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a ticket here: https://core.trac.wordpress.org/ticket/45753
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a link to the Trac ticket in cf2d701.
@@ -103,7 +103,46 @@ export function* getEmbedPreview( url ) { | |||
* Requests Upload Permissions from the REST API. | |||
*/ | |||
export function* hasUploadPermissions() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I mark this as deprecated? I'm unsure on what the policy is post 5.0 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know either. Might be worth pinging someone on slack. Will hold off approving until the answer is known.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc. @youknowriad
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably fine leaving this as is where we support both canUser
and hasUploadPermissions
. We can always deprecate later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to use a soft deprecation by using @deprecated in JSDoc and maybe filter out all actions and selectors from the generated docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, for now, we should leave those, we can add a deprecation message if needed but without specifying a version.
This is something we should discuss more in the next #core-js chats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noted this in the agenda for the next meeting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@noisysocks I noticed you @deprecated
the selector, but the resolver hasn't been deprecated. I guess technically they're the same interface and the resolver wouldn't be called directly, but I wanted to double-check if it was intentional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. I think selectors are the public interface here but it wouldn't hurt to have the @deprecated
attribute in the resolver as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 4ea46a5!
708a504
to
9ec72e8
Compare
@gziolo any thoughts on this one? |
@@ -103,7 +103,46 @@ export function* getEmbedPreview( url ) { | |||
* Requests Upload Permissions from the REST API. | |||
*/ | |||
export function* hasUploadPermissions() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to use a soft deprecation by using @deprecated in JSDoc and maybe filter out all actions and selectors from the generated docs.
packages/core-data/src/resolvers.js
Outdated
|
||
const method = methods[ action ]; | ||
if ( ! method ) { | ||
throw new Error( `'${ action }' is not a valid action` ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We aren't consistent, but probably we should end all sentences with a period.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 ccf4936
throw new Error( `'${ action }' is not a valid action` ); | ||
} | ||
|
||
const path = id ? `/wp/v2/${ resource }/${ id }` : `/wp/v2/${ resource }`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const path = id ? `/wp/v2/${ resource }/${ id }` : `/wp/v2/${ resource }`; | |
const path = '/wp/v2/${ resource }' + ( id ? `/${ id }` : '' ); |
would be shorter, not sure what reads better though :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that with the more verbose version that you can glance at the code and see the "shape" of the URL.
@@ -116,5 +155,7 @@ export function* hasUploadPermissions() { | |||
allowHeader = get( response, [ 'headers', 'Allow' ], '' ); | |||
} | |||
|
|||
yield receiveUploadPermissions( includes( allowHeader, 'POST' ) ); | |||
const key = compact( [ action, resource, id ] ).join( '/' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice trick :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd need template strings for the first part:
const key = compact( [ action, resource, id ] ).join( '/' ); | |
const path = `/wp/v2/${ resource }` + ( id ? `/${ id }` : '' ); |
I think it's a bit easier to sort out though, yeah.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this suggestion meant to go in this thread? #12378 (comment)
packages/core-data/src/resolvers.js
Outdated
yield receiveUploadPermissions( includes( allowHeader, 'POST' ) ); | ||
const key = compact( [ action, resource, id ] ).join( '/' ); | ||
const isAllowed = includes( allowHeader, method ); | ||
yield receiveUserPermissions( key, isAllowed ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be named receiveUserPermission
using singular version to reflect that you can provide only one permission at the time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the very least could we add the ticket URL in the comment so it can be tracked/checked on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be named receiveUserPermission using singular version to reflect that you can provide only one permission at the time?
👍 02d9502
At the very least could we add the ticket URL in the comment so it can be tracked/checked on?
I think this is referring to this thread? #12378 (comment) If so I added a link to the Trac ticket in
cf2d701.
const state = deepFreeze( { | ||
userPermissions: {}, | ||
} ); | ||
expect( canUser( state, 'create', 'media' ) ).toBe( true ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's surprising that we set true
as a default value. What if REST API call takes 5 seconds and UI would be rendered before that happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, true
as the default seems wrong to me. I'd rather UI update after API requests and have extra settings added than them removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed canUser()
to return false
by default in 62bcb19.
}; | ||
} ), | ||
withDispatch( ( dispatch, { onToggle = noop } ) => { | ||
withDispatch( ( dispatch, { clientId, onToggle = noop }, { select } ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
select
- that's interesting to see it have much broader application than I anticipated. I hope there are no drawbacks of that approach :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that in this case it lets us avoid passing unnecessary props to the component.
! isReusableBlock( blocks[ 0 ] ) || | ||
! getReusableBlock( blocks[ 0 ].attributes.ref ) | ||
// Show 'Convert to Regular Block' when selected block is a reusable block | ||
isVisible: isReusable || ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably better to move isVisible
outside of the object - the way it was before. It makes return value harder follow at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 1c3f722
path, | ||
// Ideally this would always be an OPTIONS request, but unfortunately there's | ||
// a bug in the REST API which causes the Allow header to not be sent on | ||
// OPTIONS requests to /posts/:id routes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is the case, it should be fixed /cc @danielbachhuber @kadamwhite
packages/core-data/src/selectors.js
Outdated
* @param {string} resource REST resource to check, e.g. 'media' or 'posts'. | ||
* @param {?string} id ID of the rest resource to check. | ||
* | ||
* @return {boolean} Whether or not the user can perform the action. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add the information about the default behavior when it is not found in the store?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Done as part of 62bcb19.
@imath - I think you implemented the original version of this behavior when working on media upload permissions. Can you double check if the generalized version is still correct? @danielbachhuber or @kadamwhite - it would be nice to get your review on the bits that interact with REST API. @noisysocks - nice work on the PR. I personally think it is quite close to be considered ready. My comments are mostly nitpicks. The only important thing from my perspective is to ensure that |
Does this change the UI at all? If so, can you include screenshots and/or GIFs in the description? |
Also:
At first glance, I'm not sure how well this |
@gziolo Hi, I've just tested the PR. I confirm that being logged in as an Administrator I'm still able to upload files and that being logged in as a contributor I can only use external URLs to add media. So it seems to be working as expected for this part 👍 @noisysocks FYI I also tested the Reusable blocks UI changes being logged in as a contributor:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good—I like the canUser
selector API, but I think there's some code cleanup here to do and I have two concerns:
- why is
true
the default response forcanUser()
? - Why are we ignoring the API request throwing an error?
|
||
* state: Data state. | ||
* action: Action to check. One of: 'create', 'read', 'update', | ||
'delete'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This indentation is kinda odd; I'm not sure why this is indented where it is...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This indentation is kinda odd; I'm not sure why this is indented where it is...
Likely because it's unprocessed from the space-aligned JSDoc. Could probably be improved with some additional processing (lines.map( trim ).join( ' ' )
?). It has no difference in the Markdown preview though, since the whitespace is collapsed.
@@ -159,6 +162,7 @@ export default compose( [ | |||
isFetching: isFetchingReusableBlock( ref ), | |||
isSaving: isSavingReusableBlock( ref ), | |||
block: reusableBlock ? getBlock( reusableBlock.clientId ) : null, | |||
canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && canUser( 'update', 'blocks', ref ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason you're checking !! reusableBlock
rather than just using reusableBlock
? Just curious!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason you're checking
!! reusableBlock
rather than just usingreusableBlock
? Just curious!
If a value is falsey in this scenario, the specific value is returned in the left-hand of the condition.
If reusableBlock
was undefined
for example, without the !!
, canUpdateBlock
would be passed as undefined
, despite its name indicating that it would be a boolean. As written, it satisfies an implicit contract of providing a boolean value. Practically speaking this would rarely have much an impact, except in a scenario where someone expected it to be the particular boolean form (=== false
).
@@ -116,5 +155,7 @@ export function* hasUploadPermissions() { | |||
allowHeader = get( response, [ 'headers', 'Allow' ], '' ); | |||
} | |||
|
|||
yield receiveUploadPermissions( includes( allowHeader, 'POST' ) ); | |||
const key = compact( [ action, resource, id ] ).join( '/' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd need template strings for the first part:
const key = compact( [ action, resource, id ] ).join( '/' ); | |
const path = `/wp/v2/${ resource }` + ( id ? `/${ id }` : '' ); |
I think it's a bit easier to sort out though, yeah.
packages/core-data/src/resolvers.js
Outdated
yield receiveUploadPermissions( includes( allowHeader, 'POST' ) ); | ||
const key = compact( [ action, resource, id ] ).join( '/' ); | ||
const isAllowed = includes( allowHeader, method ); | ||
yield receiveUserPermissions( key, isAllowed ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the very least could we add the ticket URL in the comment so it can be tracked/checked on?
parse: false, | ||
} ); | ||
} catch ( error ) { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😟 This seems like it should throw. Why are we catching an ignoring the error without doing anything with it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error
here refers to the API error that resulted from our OPTIONS request. Doing nothing is correct in this case as we want to leave the store as is. I've added a comment to explain this in e0a7269.
const state = deepFreeze( { | ||
userPermissions: {}, | ||
} ); | ||
expect( canUser( state, 'create', 'media' ) ).toBe( true ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, true
as the default seems wrong to me. I'd rather UI update after API requests and have extra settings added than them removed.
blocks.length === 1 && | ||
blocks[ 0 ] && | ||
isReusableBlock( blocks[ 0 ] ) && | ||
!! getReusableBlock( blocks[ 0 ].attributes.ref ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to use !!
here? I just always find it weird to see 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to ensure that typeof isReusable === 'boolean'
.
|
||
every( blocks, ( block ) => ( | ||
// Guard against the case where a regular block has *just* been converted | ||
!! block && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, any reason for the !!
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to ensure that typeof isVisible === 'boolean'
.
9ec72e8
to
cf2d701
Compare
15ff121
to
7b8d040
Compare
IIRC this one also needed a change in core to preload that request. |
Getting this in early so that there's time for it to bake in 5.0.
Thanks for the reminder. I wonder what the best way to do this is... Do we check how the plugin PHP files differ when updating Core's script dependencies? (e.g. |
@noisysocks Not sure. This one was done as a trac ticket, so maybe that's the best option: |
@@ -24,6 +24,7 @@ | |||
"@babel/runtime": "^7.0.0", | |||
"@wordpress/api-fetch": "file:../api-fetch", | |||
"@wordpress/data": "file:../data", | |||
"@wordpress/deprecated": "file:../deprecated", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should have been a corresponding change to the root package-lock.json
. I'm not sure how this passed the build task which checks for local uncommitted changes, but it shouldn't have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh! Looks like check-local-changes
doesn't pick this up because precheck-local-changes
doesn't run npm install
. CI doesn't pick it up because it uses npm ci
.
Will fix this up in a seperate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A test that I made: |
@paaljoachim What version are you testing against? This has only just been released in the plugin today afaik. I couldn't reproduce testing against master. |
Hey Daniel @talldan Lets just call it an error on my end. I made the following comment: |
Improves the UX of creating, editing, and deleting a reusable block when logged in as an author or contributor by disabling the _Add to Reusable Blocks_, _Edit_, and _Remove from Reusable Blocks_ buttons when necessary. This is accomplished under the hood by introducing the `canUser()` selector to `core-data` which allows callers to query whether the REST API supports performing a given action on a given resource, e.g. one can query whether the logged in user can create posts by running `wp.data.select( 'core' ).canUser( 'create', 'posts' )`. The existing `hasUploadPermissions()` selector is changed to use `canUser( 'create', 'media' )` under the hood.
Improves the UX of creating, editing, and deleting a reusable block when logged in as an author or contributor by disabling the _Add to Reusable Blocks_, _Edit_, and _Remove from Reusable Blocks_ buttons when necessary. This is accomplished under the hood by introducing the `canUser()` selector to `core-data` which allows callers to query whether the REST API supports performing a given action on a given resource, e.g. one can query whether the logged in user can create posts by running `wp.data.select( 'core' ).canUser( 'create', 'posts' )`. The existing `hasUploadPermissions()` selector is changed to use `canUser( 'create', 'media' )` under the hood.
What this is
Fixes #12338.
Improves the UX of creating, editing, and deleting a reusable block when logged in as an author or contributor by disabling the Add to Reusable Blocks, Edit, and Remove from Reusable Blocks buttons when necessary.
This is accomplished under the hood by introducing the
canUser()
selector tocore-data
which allows callers to query whether the REST API supports performing a given action on a given resource, e.g. one can query whether the logged in user can create posts by runningwp.data.select( 'core' ).canUser( 'create', 'posts' )
.The existing
hasUploadPermissions()
selector is changed to usecanUser( 'create', 'media' )
under the hood.How it looks
Add to Reusable Blocks button is visible for admins and editors:
Add to Reusable Blocks button is hidden for contributors:
Edit button is enabled and Remove from Reusable Blocks is visible for admins and editors:
Edit button is disabled and Remove from Reusable Blocks is hidden for authors:
How to test